package com.example.rotationgame;

import java.util.Timer;
import java.util.TimerTask;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Display;
import android.view.Window;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;

public class MainActivity extends Activity implements SensorEventListener{
    private SensorManager mSensorManager;
    private Sensor mAccelerometer;

    // screen size
    private int mScrWidth;
    private int mScrHeight;
    
    //physics parameters    
    //note that in an ideal model, the simulation is independent of the mass of the ball
    // imaginary friction on the screen, steel ball bearings ~0.001-0.002, 
    private final float mu = 0.08f; //roughly steel ball on hard wood
    //private final float mDeltaT = 0.02f; //time interval for integration. this should not be smaller than fastest sensor update interval
    private final float mCoR = 0.6f;	//coefficient of restitution
    private final float mBallRadius = 10f; //radius of the ball
    //meter to pixel conversion factor, to scale screensize for ball movement (as if real ball on to scale size screen), for 125 dpi screens
    private final float mMetreToPixel = 1000f;//4921.26f is for real life screen size of 125 dpi;
    //private float mAcc = 0f;	//acceleration vector in the x and y plane
    private float mVel = 0f;	//velocity in the x and y plane
    private float mAccZ = 0f;	//acceleration in the z direction due to gravity
    private float mAccXFr = 0f, mAccYFr = 0f;	//acceleration due to friction. always opposes direction of motion
    private final float mVelTol = 0.002f; //velocity tolerance, 2mm/s. Below this velocity, static friction limit applies (linearly)
    private final int mTmrDelay = 10;
    private final int mTmrInterval = 20; //timer delay and interval in ms
    private long oldEventTime = 0l;
    private float mDeltaT_Sensor = 0f;	//time interval for integration if using sensor update interval. this is periodic with jitter event
    private final float mVelTolZero = 1e-4f; //tolerance for velocity division calculation
    //private boolean firstTime;
        
    //PointF holds two float coordinates
    android.graphics.PointF mBallPos, mBallVel, mBallAcc;
    
    
    //ball stuff
    BallView mBallView = null;
    Handler RedrawHandler = new Handler(); //so redraw occurs in main thread
    Timer mTmr = null;
    TimerTask mTsk = null;
        
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // set the screen always portrait
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        requestWindowFeature(Window.FEATURE_NO_TITLE); //hide title bar
        //set app to full screen and keep screen on 
        getWindow().setFlags(0xFFFFFFFF,LayoutParams.FLAG_FULLSCREEN|LayoutParams.FLAG_KEEP_SCREEN_ON);
        
        setContentView(R.layout.activity_main);
        //create pointer to main screen
        final FrameLayout mainView = (android.widget.FrameLayout)findViewById(R.id.main_view);
        
        //get screen dimensions        
        Display display = getWindowManager().getDefaultDisplay();  
        mScrWidth = display.getWidth(); 
        mScrHeight = display.getHeight();
        //Log.d(this.toString(),Integer.toString(mScrWidth)+ " "+ Integer.toString(mScrHeight));
        
        //create variables for ball position and speed
        mBallPos = new android.graphics.PointF();
        mBallVel = new android.graphics.PointF();
        mBallAcc = new android.graphics.PointF();
        
        //initial position and velocity
        mBallPos.x = mScrWidth/2; 
        mBallPos.y = mScrHeight/2;
        
        mBallVel.x = 0; 
        mBallVel.y = 0; 
        
		//create initial ball
		mBallView = new BallView(this, mBallPos.x, mBallPos.y, mBallRadius);
        mainView.addView(mBallView); //add ball to main screen
		mBallView.invalidate(); //call onDraw in BallView
		
        // Get an instance of the SensorManager
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        //find the accelerometer sensor
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        /*if (mAccelerometer == null){
        	Log.d(this.toString(),"could not find accelerometer");
        } else {
        	Log.d(this.toString(),"fount linear accelerometer "+mAccelerometer.getName());
        }*/
        

      
    }
   
    @Override
    protected void onPause() {
        // make sure to turn our sensor off when the activity is paused
        mSensorManager.unregisterListener(this);
        mTmr.cancel(); //kill\release timer (our only background thread)
        mTmr = null;
        mTsk = null;
        super.onPause();
    }
    
    @Override
    protected void onResume() {
    	
    	//enable accelerometer sensor with GAME sampling interval
        mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
        //either fastest nor UI seems to work very well... fastest leads to brownian drift
        
        //Log.d(this.toString(),"registered listener on resume " + Boolean.toString(sensor_register_result));
        oldEventTime = 0l;
    	//create timer and timer task to move ball to new position
        mTmr = new Timer(); 
        mTsk = new TimerTask() {
        	public void run() {     		
        		//update ball class instance
        		mBallView.x = mBallPos.x;
        		mBallView.y = mBallPos.y;
        		//Log.d(this.toString(),Float.toString(mBallView.x)+" "+Float.toString(mBallView.y));
        		//redraw ball. Must run in background thread to prevent thread lock.
        		RedrawHandler.post(        		
	        		new Runnable() {
	        		    public void run() {    
	        		    	mBallView.invalidate();
	        		    }
	        		}
        		);
        	};
        };
        
        //start timer task with specified interval
        mTmr.schedule(mTsk,mTmrDelay,mTmrInterval); 
        super.onResume();    
    }
    
    public void onSensorChanged(SensorEvent event) {   
    	
    	if (oldEventTime == 0l){
    		//1st time run
    		oldEventTime = event.timestamp;
    	} else {
    		//calc time interval for integration
    		mDeltaT_Sensor = ((float)(event.timestamp - oldEventTime))/1e9f;	//convert nanoseconds to seconds
    		//Log.d(this.toString(),Long.toString(event.timestamp) + " " + Long.toString(oldEventTime) +" " +Long.toString(event.timestamp - oldEventTime) + " " + Float.toString(mDeltaT_Sensor));
    		
    		oldEventTime = event.timestamp;
    		
    		//calculate average acceleration for past interval
    		mBallAcc.x = 0.5f*(mBallAcc.x + -event.values[0]);
    		mBallAcc.y = 0.5f*(mBallAcc.y + event.values[1]);
    		mAccZ = 0.5f*(mAccZ + -event.values[2]);
    		
    		//if not using an average then the calculation of mBallAcc and mBallVel is slightly different
    		
    		//Log.d(this.toString(),"xaccl "+Float.toString(mBallAcc.x)+" yaccl "+Float.toString(mBallAcc.y) + " acc " + Float.toString(mBallAcc.length()));
    		//Log.d(this.toString(),"xvel "+Float.toString(mBallVel.x)+" yvel "+Float.toString(mBallVel.y)+ " vel "+Float.toString(mBallVel.length()));
    		    		
    		//resolve accl due to friction into x and y components in proportion to the velocity vector
    		mVel = mBallVel.length();
    		mAccZ = Math.abs(mAccZ);	//always want z acceleration as a positive number, to calculate accl due to friction  	   		
    		
    		//potential divide by 0 error!!!
    		if (mVel <= mVelTolZero) {
    			//if mAcc is close enough to 0, within tolerance, set to 0 to avoid div by 0 error
    			//Log.d(this.toString(), "close to 0");
    			mAccXFr = 0f;
    			mAccYFr = 0f;
    		} else {
    			if (mVel <= mVelTol) {
    				//within static friction range, i.e. just starting to move
    				//Log.d(this.toString(), "static friction");
    				mAccXFr = mu*mAccZ*(mBallVel.x/mVelTol);	//simplified expression. should be mu*mAccZ*(mBallVel.x/mVel)*(mVel/mVelTol)
        			mAccYFr = mu*mAccZ*(mBallVel.y/mVelTol);
    			} else {
    				//within dynamic friction range, i.e. moving quickly
    				//Log.d(this.toString(), "dynamic friction");
            		mAccXFr = mu*mAccZ*(mBallVel.x/mVel);	//quantity in brackets is a ratio and has maximum value of +-1
            		mAccYFr = mu*mAccZ*(mBallVel.y/mVel);
    			}
    		}   
    		
    		//Log.d(this.toString(),"AccXFr "+Float.toString(mAccXFr)+" AccYFr "+Float.toString(mAccYFr) + " mAccZ "+ Float.toString(mAccZ));
    		
    		//calculate the actual acceleration after friction effects
			mBallAcc.x -= mAccXFr;
			mBallAcc.y -= mAccYFr;
			//Log.d(this.toString(),"fast, xaccl "+Float.toString(mBallAcc.x)+" yaccl "+Float.toString(mBallAcc.y));

    		//update velocity using average of previous and current accelerations
    		//the result is average velocity for this time interval
    		mBallVel.x += mBallAcc.x*mDeltaT_Sensor;
    		mBallVel.y += mBallAcc.y*mDeltaT_Sensor;
    		
    		//move ball based on average velocity for this interval
    		//Log.d(this.toString(),"Pos "+Float.toString(mBallPos.x)+" "+Float.toString(mBallPos.y));
    		//beware of units, position is in pixels, whilst vel and acc are in SI units
    		mBallPos.x += (int) mMetreToPixel*(mDeltaT_Sensor * mBallVel.x); 
    		mBallPos.y += (int) mMetreToPixel*(mDeltaT_Sensor * mBallVel.y);
    	}
    	
		//update new sensor values
    	mBallAcc.x = -event.values[0];	//actual acceleration is in opposite direction of reaction force experienced by accelerometer
    	mBallAcc.y = event.values[1];	//screen y axis +ve direction is in opposite direction to accelerometer y axis +ve direction
    	mAccZ = -event.values[2];
    	
    	//Bouncing behavior
		if (mBallPos.x < mBallRadius) {
			mBallPos.x = mBallRadius;
			mBallVel.x = -mBallVel.x * mCoR;        			
		}
		if (mBallPos.x > (mScrWidth-mBallRadius)) {
			mBallPos.x = mScrWidth-mBallRadius;
			mBallVel.x = -mBallVel.x * mCoR;
		}
		
		if (mBallPos.y < mBallRadius) {
			mBallPos.y = mBallRadius;
			mBallVel.y = -mBallVel.y * mCoR;        			
		}
		if (mBallPos.y > (mScrHeight-mBallRadius)) {
			mBallPos.y = mScrHeight-mBallRadius;
			mBallVel.y = -mBallVel.y * mCoR;
		}    	
    }
    
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
    
    @Override
    public void onDestroy() //main thread stopped
    {
        super.onDestroy();
        //wait for threads to exit before clearing app
        System.runFinalizersOnExit(true); 
        //remove app from memory
        android.os.Process.killProcess(android.os.Process.myPid());  
    }
    
    //listener for config change. 
    //This is called when user tilts phone enough to trigger landscape view
    //we want our app to stay in portrait view, so bypass event 
    @Override 
    public void onConfigurationChanged(Configuration newConfig)
    {
       super.onConfigurationChanged(newConfig);
    }
    
    
}
